using System;

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using TTDotNet.Util;


namespace TTDotNet.Engine.ThreeDEngine
{
    public class AJCamera
    {

        private Matrix _viewMatrix;
        private Matrix _projectionMatrix;

        private Vector3 _right;
        private Vector3 _up;
        private Vector3 _look;
        private Vector3 _position; //private Vector3 _cameraPosition;
        private Vector3 _lookAt; //private Vector3 _cameraTarget;
        private Vector3 _velocity;

        private float width;
        private float height;

        private float _yaw; //private float _heading;
        private float _pitch;
        private float _roll;

        private float _fieldOfView = (float)Math.PI / 4.0f;
        private float _nearPlane = 0.1f;
        private float _farPlane = 100000.0f;

        private Vector3[] _frustumCorners;
        private Plane[] _frustumPlanes;

        private float _maxPitch = Geometry.DegreeToRadian(80.0f);
        private float _maxVelocity = 3.0f;

        private bool staticCamera;

        Vector3 newPos = new Vector3(2, 0, 2);

        public AJCamera(float a, float b, bool staticCam)
        {
            this.staticCamera = staticCam;

            this.width = a;
            this.height = b;

            _viewMatrix = Matrix.Identity;
            _projectionMatrix = Matrix.PerspectiveFovLH(_fieldOfView, a / b, _nearPlane, _farPlane);

            _position = new Vector3(-4.0f, 8.0f, -4.0f);
            _velocity = new Vector3();
            _look = new Vector3(1.0f, -1.5f, 1.0f);

            _frustumCorners = new Vector3[8];
            _frustumPlanes = new Plane[6];

            Update();
        }

        public void ChangeAspectRatio(float width, float height)
        {
            _projectionMatrix = Matrix.PerspectiveFovLH(_fieldOfView, width / height, _nearPlane, _farPlane);
        }

        public void Update()
        {
            if (Vector3.Length(_velocity) > _maxVelocity)
                _velocity = Vector3.Normalize(_velocity) * _maxVelocity;

            // Update 
            _position += _velocity;

            if (_position.Y > 250)
            {
                _position -= _velocity;
            }

            float tileHeight = 0;

            if(Game.gameWorld != null)
                tileHeight = Game.gameWorld.Map.GetTileMaxHeight((int)Position.X, (int)Position.Z) / Game.heightScale;

            if (_position.Y < 1f + tileHeight)
            {
                _position.Y = 1f + tileHeight;
            }

            // Stop
            _velocity = new Vector3();

            if(!staticCamera) 
            _lookAt = _position + _look;

            Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);
            _viewMatrix = Matrix.LookAtLH(_position, _lookAt, up);

            _right.X = _viewMatrix.M11;
            _right.Y = _viewMatrix.M21;
            _right.Z = _viewMatrix.M31;
            _up.X = _viewMatrix.M12;
            _up.Y = _viewMatrix.M22;
            _up.Z = _viewMatrix.M32;
            _look.X = _viewMatrix.M13;
            _look.Y = _viewMatrix.M23;
            _look.Z = _viewMatrix.M33;

            float lookLengthOnXZ = (float)Math.Sqrt(_look.Z * _look.Z + _look.X * _look.X);
            _pitch = (float)Math.Atan2(_look.Y, lookLengthOnXZ);
            _yaw = (float)Math.Atan2(_look.X, _look.Z);
            _roll = (float)Math.Atan2(_look.Z, lookLengthOnXZ);

            ComputeViewFrustum();
        }

        public void MoveForward(float units)
        {
            // Move forward and back
            _velocity += _look * units;
            _velocity.Y = 0;
            Update();
        }

        public void Strafe(float units)
        {
            // Move left and right
            _velocity += _right * units;
            Update();
        }

        public void Zoom(float units)
        {
            _velocity += _look * units;
            Update();
        }

        public void UpDown(float units)
        {
            // Move up and down
            _velocity.Y += units;
            Update();
        }

        public void AdjustYaw(float radians)
        {
            if (radians == 0.0f)
                return;

            Matrix rotation = Matrix.RotationAxis(new Vector3(0, 1, 0), radians);
            _look = Vector3.TransformNormal(_look, rotation);
            _right = Vector3.TransformNormal(_right, rotation);

            Update();
        }

        public void RotateAroundLook(float radians)
        {
            float diff = _position.Y / -_look.Y;
            Vector3 groundPoint = Vector3.Add(_position, Vector3.Multiply(_look, diff));

            Matrix rotation = Matrix.RotationY(radians);

            Vector3 diff2 = Vector3.Subtract(_position, groundPoint);

            Vector3 diff3 = Vector3.TransformNormal(diff2, rotation);

            _position = Vector3.Add(groundPoint, diff3);
            _look = Vector3.TransformNormal(_look, rotation);

            Update();
        }

        public void AdjustPitch(float radians)
        {
            if (radians == 0.0f)
                return;

            _pitch -= radians;

            //if (_pitch > _maxPitch)
            //{
            //    radians += _pitch - _maxPitch;
            //}
            if (_pitch < -_maxPitch)
            {
                radians += _pitch + _maxPitch;
            }

            Matrix rotation = Matrix.RotationAxis(_right, radians);
            _look = Vector3.TransformNormal(_look, rotation);
            _up = Vector3.TransformNormal(_up, rotation);

            Update();
        }

        public void AdjustRoll(float radians)
        {
            if (radians == 0.0f)
                return;

            Matrix rotation = Matrix.RotationAxis(_look, radians);
            _right = Vector3.TransformNormal(_right, rotation);
            _up = Vector3.TransformNormal(_up, rotation);
        }
        
        private void ComputeViewFrustum()
        {
            Matrix matrix = _viewMatrix * _projectionMatrix;
            matrix.Invert();

            _frustumCorners[0] = new Vector3(-1.0f, -1.0f, 0.0f); // xyz
            _frustumCorners[1] = new Vector3(1.0f, -1.0f, 0.0f); // Xyz
            _frustumCorners[2] = new Vector3(-1.0f, 1.0f, 0.0f); // xYz
            _frustumCorners[3] = new Vector3(1.0f, 1.0f, 0.0f); // XYz
            _frustumCorners[4] = new Vector3(-1.0f, -1.0f, 1.0f); // xyZ
            _frustumCorners[5] = new Vector3(1.0f, -1.0f, 1.0f); // XyZ
            _frustumCorners[6] = new Vector3(-1.0f, 1.0f, 1.0f); // xYZ
            _frustumCorners[7] = new Vector3(1.0f, 1.0f, 1.0f); // XYZ

            for (int i = 0; i < _frustumCorners.Length; i++)
                _frustumCorners[i] = Vector3.TransformCoordinate(_frustumCorners[i], matrix);

            // Now calculate the planes
            _frustumPlanes[0] = Plane.FromPoints(_frustumCorners[0], _frustumCorners[1], _frustumCorners[2]); // Near
            _frustumPlanes[1] = Plane.FromPoints(_frustumCorners[6], _frustumCorners[7], _frustumCorners[5]); // Far
            _frustumPlanes[2] = Plane.FromPoints(_frustumCorners[2], _frustumCorners[6], _frustumCorners[4]); // Left
            _frustumPlanes[3] = Plane.FromPoints(_frustumCorners[7], _frustumCorners[3], _frustumCorners[5]); // Right
            _frustumPlanes[4] = Plane.FromPoints(_frustumCorners[2], _frustumCorners[3], _frustumCorners[6]); // Top
            _frustumPlanes[5] = Plane.FromPoints(_frustumCorners[1], _frustumCorners[0], _frustumCorners[4]); // Bottom
        }

        public bool IsInViewFrustum(AJObject unitToCheck)
        {
            //System.Windows.Forms.MessageBox.Show("hey");

            foreach (Plane plane in _frustumPlanes)
            {
                if (plane.A * unitToCheck.GetPosition().X + plane.B * unitToCheck.GetPosition().Y + plane.C * unitToCheck.GetPosition().Z + plane.D <= (-unitToCheck.radius))
                    return false;
            }

            return true;
        }

        public bool IsInViewFrustum(float x, float y, float z, float radius)
        {
            foreach (Plane plane in _frustumPlanes)
            {
                if (plane.A * x + plane.B * y + plane.C * z + plane.D <= (-radius))
                    return false;
            }

            return true;
        }

        public bool IsInViewFrustum2(float x, float y, float z, float radius)
        {
            float distance = 0.0f;

            foreach (Plane plane in _frustumPlanes)
            {
                distance = plane.Dot(new Vector3(x, y, z));

                if (distance <= (-radius))
                    return false;
            }
            return true;
        }

        /*
        public bool IsInViewFrustum2(UnitBase unitToCheck)
        {
            float distance = 0.0f;

            foreach (Plane plane in _frustumPlanes)
            {
                distance = plane.Dot(unitToCheck.Position);

                if (distance <= (-unitToCheck.Radius))
                    return false;
            }
            return true;
        }
        */

        public float GetWidth()
        {
            return width;
        }

        public float GetHeight()
        {
            return height;
        }

        public float Heading
        {
            get
            {
                return Geometry.RadianToDegree(_yaw);
            }
        }

        public float Pitch
        {
            get { return Geometry.RadianToDegree(_pitch); }
            set { _pitch = value; }
        }

        public float Roll
        {
            get { return Geometry.RadianToDegree(_roll); }
            set { _roll = value; }
        }

        public Matrix View
        {
            get { return _viewMatrix; }
        }

        public Matrix Projection
        {
            get { return _projectionMatrix; }
        }

        public Vector3 Position
        {
            get { return _position; }
            set { _position = value; }
        }

        public Vector3 LookAt
        {
            get { return _lookAt; }
            set { _lookAt = value; }
        }

        public float MaxVelocity
        {
            get { return _maxVelocity; }
            set { _maxVelocity = value; }
        }

        public float FarClipPlane
        {
            get { return _farPlane; }
            set { _farPlane = value; }
        }

    }
}
